#ifndef __ARCH_MPU_H__
#define __ARCH_MPU_H__

#include <sys/types.h>
#include <stdint.h>
#include <stdbool.h>
#include <arch.h>

#define MPU_TYPE_DREGION_Pos    8U
#define MPU_TYPE_DREGION_Msk    (0xFFUL << MPU_TYPE_DREGION_Pos)

/* Protection Region Base Address Register */
#define MPU_PRBAR_ADDR_Pos               (6)
#define MPU_PRBAR_ADDR_Msk               (0x3ffffffUL << MPU_PRBAR_ADDR_Pos)
#define MPU_PRBAR_XN_Pos               (0)
#define MPU_PRBAR_XN_Msk               (0x1UL << MPU_PRBAR_XN_Pos)
#define MPU_PRBAR_AP_Pos               (1)
#define MPU_PRBAR_AP_Msk               (0x3UL << MPU_PRBAR_AP_Pos)
#define MPU_PRBAR_SH_Pos               (3)
#define MPU_PRBAR_SH_Msk               (0x3UL << MPU_PRBAR_SH_Pos)

/* Protection Region Limit Address Register */
#define MPU_PRLAR_LIMIT_Pos             6U
#define MPU_PRLAR_LIMIT_Msk             (0x3ffffffUL << MPU_PRLAR_LIMIT_Pos)
#define MPU_PRLAR_AttrIndx_Pos          1U
#define MPU_PRLAR_AttrIndx_Msk          (0x7UL << MPU_PRLAR_AttrIndx_Pos)
#define MPU_PRLAR_ENABLE_Msk            (1)

/* Attribute flag for not-allowing execution (eXecute Never) */
#define NOT_EXEC MPU_PRBAR_XN_Msk

/* Read/write at EL1, no access at EL0 */
#define P_RW_U_NA       0x0
#define P_RW_U_NA_Msk   ((P_RW_U_NA << MPU_PRBAR_AP_Pos) & MPU_PRBAR_AP_Msk)
/* Read/write at EL1 and EL0 */
#define P_RW_U_RW       0x1U
#define P_RW_U_RW_Msk   ((P_RW_U_RW << MPU_PRBAR_AP_Pos) & MPU_PRBAR_AP_Msk)
/* Read-only at EL1, no access at EL0 */
#define P_RO_U_NA       0x2
#define P_RO_U_NA_Msk   ((P_RO_U_NA << MPU_PRBAR_AP_Pos) & MPU_PRBAR_AP_Msk)
/* Read-only at EL1 and EL0 */
#define P_RO_U_RO       0x3U
#define P_RO_U_RO_Msk   ((P_RO_U_RO << MPU_PRBAR_AP_Pos) & MPU_PRBAR_AP_Msk)

/* Attribute flags for share-ability */
#define NON_SHAREABLE   0x0U
#define NON_SHAREABLE_Msk   \
    ((NON_SHAREABLE << MPU_PRBAR_SH_Pos) & MPU_PRBAR_SH_Msk)
#define OUTER_SHAREABLE 0x2U
#define OUTER_SHAREABLE_Msk \
    ((OUTER_SHAREABLE << MPU_PRBAR_SH_Pos) & MPU_PRBAR_SH_Msk)
#define INNER_SHAREABLE 0x3U
#define INNER_SHAREABLE_Msk \
    ((INNER_SHAREABLE << MPU_PRBAR_SH_Pos) & MPU_PRBAR_SH_Msk)



/* The following definitions are for internal use in arm_mpu.h. */
#define STRONGLY_ORDERED_SHAREABLE      MPU_RASR_S_Msk
#define DEVICE_SHAREABLE                (MPU_RASR_B_Msk | MPU_RASR_S_Msk)
#define NORMAL_OUTER_INNER_WRITE_THROUGH_SHAREABLE \
		(MPU_RASR_C_Msk | MPU_RASR_S_Msk)
#define NORMAL_OUTER_INNER_WRITE_THROUGH_NON_SHAREABLE	MPU_RASR_C_Msk
#define NORMAL_OUTER_INNER_WRITE_BACK_SHAREABLE	\
		(MPU_RASR_C_Msk | MPU_RASR_B_Msk | MPU_RASR_S_Msk)
#define NORMAL_OUTER_INNER_WRITE_BACK_NON_SHAREABLE \
		(MPU_RASR_C_Msk | MPU_RASR_B_Msk)
#define NORMAL_OUTER_INNER_NON_CACHEABLE_SHAREABLE \
		((1 << MPU_RASR_TEX_Pos) | MPU_RASR_S_Msk)
#define NORMAL_OUTER_INNER_NON_CACHEABLE_NON_SHAREABLE \
		(1 << MPU_RASR_TEX_Pos)
#define NORMAL_OUTER_INNER_WRITE_BACK_WRITE_READ_ALLOCATE_SHAREABLE \
	((1 << MPU_RASR_TEX_Pos) |\
	 MPU_RASR_C_Msk | MPU_RASR_B_Msk | MPU_RASR_S_Msk)
#define NORMAL_OUTER_INNER_WRITE_BACK_WRITE_READ_ALLOCATE_NON_SHAREABLE \
	((1 << MPU_RASR_TEX_Pos) | MPU_RASR_C_Msk | MPU_RASR_B_Msk)
#define DEVICE_NON_SHAREABLE            (2 << MPU_RASR_TEX_Pos)

/* Some helper defines for common regions */
#define REGION_RAM_ATTR(size) \
{ \
	(NORMAL_OUTER_INNER_WRITE_BACK_WRITE_READ_ALLOCATE_NON_SHAREABLE | \
	 MPU_PRBAR_XN_Msk | size | P_RW_U_NA_Msk) \
}
#define REGION_RAM_NOCACHE_ATTR(size) \
{ \
	(NORMAL_OUTER_INNER_NON_CACHEABLE_NON_SHAREABLE | \
	 MPU_PRBAR_XN_Msk | size | P_RW_U_NA_Msk) \
}
#define REGION_PPB_ATTR(size) { (STRONGLY_ORDERED_SHAREABLE | size | \
		P_RW_U_NA_Msk) }
#define REGION_IO_ATTR(size) { (DEVICE_NON_SHAREABLE | size | P_RW_U_NA_Msk) }

/* Read-Write access permission attributes */
#define _MEM_PARTITION_P_NA_U_NA	(NO_ACCESS_Msk | NOT_EXEC)
#define _MEM_PARTITION_P_RW_U_RW	(P_RW_U_RW_Msk | NOT_EXEC)
#define _MEM_PARTITION_P_RW_U_RO	(P_RW_U_RO_Msk | NOT_EXEC)
#define _MEM_PARTITION_P_RW_U_NA	(P_RW_U_NA_Msk | NOT_EXEC)
#define _MEM_PARTITION_P_RO_U_RO	(P_RO_U_RO_Msk | NOT_EXEC)
#define _MEM_PARTITION_P_RO_U_NA	(P_RO_U_NA_Msk | NOT_EXEC)

/* Execution-allowed attributes */
#define _MEM_PARTITION_P_RWX_U_RWX (P_RW_U_RW_Msk)
#define _MEM_PARTITION_P_RWX_U_RX  (P_RW_U_RO_Msk)
#define _MEM_PARTITION_P_RX_U_RX   (P_RO_U_RO_Msk)

/* Kernel macros for memory attribution
 * (access permissions and cache-ability).
 *
 * The macros are to be stored in mem_partition_attr_t
 * objects. The format of mem_partition_attr_t is an
 * "1-1" mapping of the ARMv7-M MPU RASR attribute register
 * fields (excluding the <size> and <enable> bit-fields).
 */


/* Read-Write access permission attributes (default cache-ability) */
#define MEM_PARTITION_P_NA_U_NA	((mem_partition_attr_t) \
	{ _MEM_PARTITION_P_NA_U_NA | \
	  NORMAL_OUTER_INNER_WRITE_BACK_WRITE_READ_ALLOCATE_NON_SHAREABLE})
#define MEM_PARTITION_P_RW_U_RW	((mem_partition_attr_t) \
	{ _MEM_PARTITION_P_RW_U_RW | \
	  NORMAL_OUTER_INNER_WRITE_BACK_WRITE_READ_ALLOCATE_NON_SHAREABLE})
#define MEM_PARTITION_P_RW_U_RO	((mem_partition_attr_t) \
	{ _MEM_PARTITION_P_RW_U_RO | \
	  NORMAL_OUTER_INNER_WRITE_BACK_WRITE_READ_ALLOCATE_NON_SHAREABLE})
#define MEM_PARTITION_P_RW_U_NA	((mem_partition_attr_t) \
	{ _MEM_PARTITION_P_RW_U_NA | \
	  NORMAL_OUTER_INNER_WRITE_BACK_WRITE_READ_ALLOCATE_NON_SHAREABLE})
#define MEM_PARTITION_P_RO_U_RO	((mem_partition_attr_t) \
	{ _MEM_PARTITION_P_RO_U_RO | \
	  NORMAL_OUTER_INNER_WRITE_BACK_WRITE_READ_ALLOCATE_NON_SHAREABLE})
#define MEM_PARTITION_P_RO_U_NA	((mem_partition_attr_t) \
	{ _MEM_PARTITION_P_RO_U_NA | \
	  NORMAL_OUTER_INNER_WRITE_BACK_WRITE_READ_ALLOCATE_NON_SHAREABLE})

/* Execution-allowed attributes (default-cacheability) */
#define MEM_PARTITION_P_RWX_U_RWX	((mem_partition_attr_t) \
	{ _MEM_PARTITION_P_RWX_U_RWX | \
	  NORMAL_OUTER_INNER_WRITE_BACK_WRITE_READ_ALLOCATE_NON_SHAREABLE})
#define MEM_PARTITION_P_RWX_U_RX	((mem_partition_attr_t) \
	{ _MEM_PARTITION_P_RWX_U_RX | \
	  NORMAL_OUTER_INNER_WRITE_BACK_WRITE_READ_ALLOCATE_NON_SHAREABLE})
#define MEM_PARTITION_P_RX_U_RX	((mem_partition_attr_t) \
	{ _MEM_PARTITION_P_RX_U_RX | \
	  NORMAL_OUTER_INNER_WRITE_BACK_WRITE_READ_ALLOCATE_NON_SHAREABLE})

/*
 * @brief Evaluate Write-ability
 *
 * Evaluate whether the access permissions include write-ability.
 *
 * @param attr The mem_partition_attr_t object holding the
 *             MPU attributes to be checked against write-ability.
 */
#define MEM_PARTITION_IS_WRITABLE(attr) \
	({ \
		int __is_writable__; \
		switch (attr.rasr_attr & MPU_PRBAR_AP_Msk) { \
		case P_RW_U_RW_Msk: \
		case P_RW_U_RO_Msk: \
		case P_RW_U_NA_Msk: \
			__is_writable__ = 1; \
			break; \
		default: \
			__is_writable__ = 0; \
		} \
		__is_writable__; \
	})

/*
 * @brief Evaluate Execution allowance
 *
 * Evaluate whether the access permissions include execution.
 *
 * @param attr The mem_partition_attr_t object holding the
 *             MPU attributes to be checked against execution
 *             allowance.
 */
#define MEM_PARTITION_IS_EXECUTABLE(attr) \
	(!((attr.rasr_attr) & (NOT_EXEC)))

/* Attributes for no-cache enabling (share-ability is selected by default) */

#define MEM_PARTITION_P_NA_U_NA_NOCACHE ((mem_partition_attr_t) \
	{(_MEM_PARTITION_P_NA_U_NA \
	| NORMAL_OUTER_INNER_NON_CACHEABLE_SHAREABLE)})
#define MEM_PARTITION_P_RW_U_RW_NOCACHE ((mem_partition_attr_t) \
	{(_MEM_PARTITION_P_RW_U_RW \
	| NORMAL_OUTER_INNER_NON_CACHEABLE_SHAREABLE)})
#define MEM_PARTITION_P_RW_U_RO_NOCACHE ((mem_partition_attr_t) \
	{(_MEM_PARTITION_P_RW_U_RO \
	| NORMAL_OUTER_INNER_NON_CACHEABLE_SHAREABLE)})
#define MEM_PARTITION_P_RW_U_NA_NOCACHE ((mem_partition_attr_t) \
	{(_MEM_PARTITION_P_RW_U_NA \
	| NORMAL_OUTER_INNER_NON_CACHEABLE_SHAREABLE)})
#define MEM_PARTITION_P_RO_U_RO_NOCACHE ((mem_partition_attr_t) \
	{(_MEM_PARTITION_P_RO_U_RO \
	| NORMAL_OUTER_INNER_NON_CACHEABLE_SHAREABLE)})
#define MEM_PARTITION_P_RO_U_NA_NOCACHE ((mem_partition_attr_t) \
	{(_MEM_PARTITION_P_RO_U_NA \
	| NORMAL_OUTER_INNER_NON_CACHEABLE_SHAREABLE)})

#define MEM_PARTITION_P_RWX_U_RWX_NOCACHE ((mem_partition_attr_t) \
	{(_MEM_PARTITION_P_RWX_U_RWX \
	| NORMAL_OUTER_INNER_NON_CACHEABLE_SHAREABLE)})
#define MEM_PARTITION_P_RWX_U_RX_NOCACHE  ((mem_partition_attr_t) \
	{(_MEM_PARTITION_P_RWX_U_RX  \
	| NORMAL_OUTER_INNER_NON_CACHEABLE_SHAREABLE)})
#define MEM_PARTITION_P_RX_U_RX_NOCACHE   ((mem_partition_attr_t) \
	{(_MEM_PARTITION_P_RX_U_RX   \
	| NORMAL_OUTER_INNER_NON_CACHEABLE_SHAREABLE)})

typedef struct {
    uint32_t rbar_attr;
    uint32_t mair_idx;
} mem_partition_attr_t;

typedef struct {
    uintptr_t start;
    size_t size;
    mem_partition_attr_t attr;
} mpu_partition_t;


typedef struct {
    /* Attributes belonging to PRBAR - AP, XN, SH */
    uint32_t xn;
    uint32_t ap;
    uint32_t sh;
    /* Attributes belonging to PRLAR - AttrIdx */
    uint32_t mair_idx;
} arm_mpu_region_attr_t;

/* Region definition data structure */
struct arm_mpu_region {
    /* Region Base Address */
    uint32_t base;
    /* Region limit */
    uint32_t limit;
    /* Region Name */
    const char *name;
    /* Region Attributes */
    arm_mpu_region_attr_t attr;
};

/* MPU configuration data structure */
struct arm_mpu_config {
    /* Number of regions */
    uint32_t num_regions;
    /* Regions */
    const struct arm_mpu_region *mpu_regions;
};

#define MPU_REGION_ENTRY(_name, _base, _limit, _attr)   \
    {                                                   \
        .name = _name,                                  \
        .base = _base,                                  \
        .limit = _limit,                                \
        .attr = _attr,                                  \
    }

extern const struct arm_mpu_config plat_mpu_config;
extern void plat_mpu_mair_config(void);

int arm_mpu_init(void);

#endif /* __ARCH_MPU_H__ */
